package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.elastic.SkuElasticModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.ElasticConfig;
import com.atguigu.gulimall.search.constant.ElasticConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.beans.Encoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author LiDeJie
 * @Create 2024/1/13 15:33
 * @Version 1.0
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    
    @Autowired
    RestHighLevelClient restHighLevelClient;
    
    @Autowired
    ProductFeignService productFeignService;
    
    
    @Override
    public SearchResp search(SearchParam searchParam) {
        //1、构建查询条件
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //2、构建聚合条件
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(searchRequest, ElasticConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("Es查询失败, request Parameters: {}, response result: {}, error: {}", searchRequest.toString(),response.toString(), e.getMessage());
        }
        //3、构建响应结果
        SearchResp searchResp = buildSearchResponse(response, searchParam);
        log.info("searchResp: {}", JSONObject.toJSONString(searchResp));
        return searchResp;
    }
   
    /**
     * 构建响应结果
     */
    private SearchResp buildSearchResponse(SearchResponse response, SearchParam searchParam) {
    
        SearchResp searchResp = new SearchResp();
        SearchHits hits = response.getHits();
        if (hits.getHits() == null || hits.getHits().length == 0) {
            return searchResp;
        }
        //sku信息
        List<SkuElasticModel> skuElasticModels = Arrays.stream(hits.getHits()).map(item -> {
            try {
                String skuInfo = item.getSourceAsString();
                SkuElasticModel skuElasticModel = JSONObject.parseObject(skuInfo, SkuElasticModel.class);
                if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                    HighlightField highlightField = item.getHighlightFields().get("skuTitle");
                    String highlightResult = highlightField.getFragments()[0].string();
                    skuElasticModel.setSkuTitle(highlightResult);
                }
                return skuElasticModel;
            } catch (Exception e) {
                e.printStackTrace();
                log.error("hit result data transfer skuModel field, hit skuResult: {}, error: {}", item.getSourceAsString(), e.getMessage());
            }
            return null;
        }).filter(item -> item != null).collect(Collectors.toList());
        searchResp.setProducts(skuElasticModels);
    
        //分页信息
        searchResp.setPageNum(searchParam.getPageNum());
        long total = hits.getTotalHits().value;
        searchResp.setTotal(total);
        //总页数：total % pageSize == 0 total / pageSize ? total / pageSize + 1
        Integer totalPages = (int) total % ElasticConstant.PRODUCT_SIZE == 0 ? (int) total / ElasticConstant.PRODUCT_SIZE : (int) total / ElasticConstant.PRODUCT_SIZE + 1;
        searchResp.setTotalPages(totalPages);
        List<Integer> pageVavs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageVavs.add(i);
        }
        searchResp.setPageNavs(pageVavs);
        
        //分类信息
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
        List<SearchResp.CatalogVo> catalogVos = catalogAgg.getBuckets().stream().map(item -> {
            SearchResp.CatalogVo catalogVo = new SearchResp.CatalogVo();
            String keyAsString = item.getKeyAsString();
            ParsedStringTerms catalogNameAgg = (ParsedStringTerms) (item.getAggregations().get("catalog_name_agg"));
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));
            catalogVo.setCatalogName(catalogName);
            return catalogVo;
        }).collect(Collectors.toList());
        searchResp.setCatalogVos(catalogVos);
        //品牌信息
        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        List<SearchResp.BrandVo> brandVos = brandAgg.getBuckets().stream().map(bucket -> {
            long brandId = bucket.getKeyAsNumber().longValue();
            String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
            SearchResp.BrandVo brandVo = new SearchResp.BrandVo();
            brandVo.setBrandId(brandId);
            brandVo.setBrandName(brandName);
            brandVo.setBrandImg(brandImg);
            return brandVo;
        }).collect(Collectors.toList());
        searchResp.setBrandVos(brandVos);
        
        //属性信息
        ParsedNested attrAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        List<SearchResp.Attrs> attrs = attrIdAgg.getBuckets().stream().map(bucket -> {
            long attrId = bucket.getKeyAsNumber().longValue();
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            List<String> attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
                String attrValue = item.getKeyAsString();
                return attrValue;
            }).collect(Collectors.toList());
        
            SearchResp.Attrs attr = new SearchResp.Attrs();
            attr.setAttrId(attrId);
            attr.setAttrName(attrName);
            attr.setAttrValue(attrValues);
            return attr;
        }).collect(Collectors.toList());
        searchResp.setAttrs(attrs);
    
        //属性面包屑: 当页面请求参数中携带attrs=1_1TB 属性参数，进行去除，然后返回新的请求参数
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())) {
            List<SearchResp.NavVo> navVoList = searchParam.getAttrs().stream().map(attr -> {
                SearchResp.NavVo navVo = new SearchResp.NavVo();
                String[] paramSplit = attr.split("_");
                searchResp.getAttrIds().add(Long.valueOf(paramSplit[0]));
                R attrResult = productFeignService.info(Long.parseLong(paramSplit[0]));
                if (attrResult.getCode() != 0 || attrResult.getData("attr", new TypeReference<AttrVoResp>() {
                }) == null) {
                    navVo.setNavName(paramSplit[0]);
                } else {
                    AttrVoResp attrVoResp = attrResult.getData("attr", new TypeReference<AttrVoResp>() {
                    });
                    navVo.setNavName(attrVoResp.getAttrName());
                    navVo.setNavValue(paramSplit[1]);
                    //浏览器路径参数
                    String queryString = getQueryString(searchParam, attr, "attrs");
                    //设置新链接
                    navVo.setLink("http://search.gmall.com/list.html?" + queryString);
                }
                return navVo;
            }).collect(Collectors.toList());
            
            searchResp.setNavs(navVoList);
        }
    
    
        //品牌面包屑：&brandId=9&brandId=9
        List<Long> brandIds = searchParam.getBrandId();
        if (!StringUtils.isEmpty(brandIds)) {
            SearchResp.NavVo navVo = new SearchResp.NavVo();
            navVo.setNavName("品牌");
            R result = productFeignService.getBrandIds(brandIds);
            if (result.getCode() == 0) {
                List<BrandVo> brands = result.getData("brands", new TypeReference<List<BrandVo>>() {});
                StringBuffer buffer = new StringBuffer();
                String brandIdQueryString = "";
                int index = 1;
                for (BrandVo brand : brands) {
                    if (index == brands.size()) {
                        buffer.append(brand.getName());
                    }else {
                        buffer.append(brand.getName() + ";");
                    }
                    brandIdQueryString = getQueryString(searchParam, String.valueOf(brand.getBrandId()), "brandId");
                }
                navVo.setNavValue(buffer.toString());
                navVo.setLink("http://search.gmall.com/list.html?" + brandIdQueryString);
            }
            searchResp.getNavs().add(navVo);
        }
    
        
        //分类面包屑：&catalogId=225
        if (searchParam.getCatalogId() != null) {
            SearchResp.NavVo navVo = new SearchResp.NavVo();
            navVo.setNavName(String.valueOf(searchParam.getCatalogId()));
            R catalogResult = productFeignService.catalogInfo(searchParam.getCatalogId());
            if (catalogResult.getCode() == 0) {
                CatalogVo catalogVo = catalogResult.getData(new TypeReference<CatalogVo>() {});
                navVo.setNavValue(catalogVo.getName());
                String replaceQuery = searchParam.get_queryString().replace("catalogId=" + searchParam.getCatalogId(), "");
                navVo.setLink("http://search.gmall.com/list.html?" + replaceQuery);
            }
            searchResp.getNavs().add(navVo);
        }
        return searchResp;
    }
    
    private void attrCrumbs(SearchParam searchParam, SearchResp searchResp) {
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())) {
            List<SearchResp.NavVo> navVoList = searchParam.getAttrs().stream().map(attr -> {
                SearchResp.NavVo navVo = new SearchResp.NavVo();
                //硬盘：1_1TB  内存：2_32G
                String[] splitAttr = attr.split("_");
                String attrId = splitAttr[0];
                //调用商品服务获取属性名称
                R attrResult = productFeignService.info(Long.parseLong(attrId));
                if (attrResult.getCode() == 0) {
                    AttrVoResp attrVoResp = attrResult.getData("attr", new TypeReference<AttrVoResp>() {
                    });
                    navVo.setNavName(attrVoResp.getAttrName());
                    navVo.setNavValue(splitAttr[1]);
                    String neqQuery = getQueryString(searchParam, attr, "attrs");
                    //http://search.gmall.com/list.html?catalog3Id=225&attrs=3_5.7%E8%8B%B1%E5%AF%B8
                    navVo.setLink("http://search.gmall.com/list.html?" + neqQuery);
                    return navVo;
                }else {
                    navVo.setNavValue(splitAttr[0]);
                }
                return navVo;
            }).collect(Collectors.toList());
    
            searchResp.setNavs(navVoList);
        }
    }
    
    
    private static String getQueryString(SearchParam searchParam, String attr, String name) {
        String encodeAttr= null;
        try {
            //对页面传递的路径参数进行编码
            encodeAttr = URLEncoder.encode(attr, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("URLEncoder.encode field: {}, error: {}", attr, e.getMessage());
        }
        String neqQuery = searchParam.get_queryString().replace("&"+name+"=" + encodeAttr, "");
        return neqQuery;
    }
    
    /**
     * 构建查询条件
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1、构建请求对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        //构建查询条件
        BoolQueryBuilder boolQuery = boolQueryBuilder(searchParam);
        
        sourceBuilder.query(boolQuery);
    
        //构建其他条件
        otherBuilder(searchParam, sourceBuilder);
    
        //聚合查询
        aggregationBuilder(sourceBuilder);
    
        SearchRequest searchRequest = new SearchRequest(new String[]{ElasticConstant.PRODUCT_INDEX}, sourceBuilder);
        log.info("构建DSL语句：{}", sourceBuilder.toString());
        return searchRequest;
    }
    
    /**
     * 构建查询条件
     */
    private static BoolQueryBuilder boolQueryBuilder(SearchParam searchParam) {
        //2、构建查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        //2.1、根据标题进行检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }
        
        //2.2、根据分类Id进行检索
        if (!ObjectUtils.isEmpty(searchParam.getCatalogId())) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalogId()));
        }
        
        //2.3、根据品牌Id进行检索
        if (!CollectionUtils.isEmpty(searchParam.getBrandId())) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandId()));
        }
        
        //2.4、是否有库存
        if (!ObjectUtils.isEmpty(searchParam.getHasStock())) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1 ? true : false));
        }
        
        
        //2.5、根据价格范围进行检索
        //价格区间 0_4999 _4999 2999_ skuPrice;
        if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
            String[] skuPrices = searchParam.getSkuPrice().split("_");
            if (skuPrices.length == 2) {
                boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPrices[0]).lte(skuPrices[1]));
            }
            
            if (skuPrices.length == 1) {
                if (searchParam.getSkuPrice().startsWith("_")) {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(skuPrices[0]));
                }
                
                if (searchParam.getSkuPrice().endsWith("_")) {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(skuPrices[0]));
                }
            }
            
        }
        
        
        //2.6、查询嵌套属性
        //销售属性 attrs=1_3G:4G:5G attrs=2_白色:黑色 attrs=3_屏幕尺寸
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())) {
            for (String attrs : searchParam.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] attr = attrs.split("_");
                if (attr != null && attr.length > 0) {
                    nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrId", attr[0]));
                    nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attr[1].split(":")));
                }
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        return boolQuery;
    }
    
    /**
     * 分页、排序、高亮
     */
    private static void otherBuilder(SearchParam searchParam, SearchSourceBuilder sourceBuilder) {
        //分页
        Integer pageNum = searchParam.getPageNum() == null ? 1 : searchParam.getPageNum();
        sourceBuilder.from((pageNum - 1) * ElasticConstant.PRODUCT_SIZE);
        sourceBuilder.size(ElasticConstant.PRODUCT_SIZE);
        
        //排序
        if (!StringUtils.isEmpty(searchParam.getSort())) {
            String[] sort = searchParam.getSort().split("_");
            SortOrder desc = sort[1].equals("desc") ? SortOrder.DESC : SortOrder.ASC;
            sourceBuilder.sort(sort[0], desc);
        }
        
        //高亮
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style = 'color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }
    }
    
    /**
     * 构建聚合条件
     */
    private static void aggregationBuilder(SearchSourceBuilder sourceBuilder) {
        //分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalogAgg);
        
        //品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(20);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);
        
        //属性聚合
        NestedAggregationBuilder nestedAggBuilder = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        nestedAggBuilder.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(nestedAggBuilder);
    }
}
